﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace NanoMvvm.ComponentModel
{
    public class DelegatePropertyDescriptor : WrapperPropertyDescriptorBase
    {
        public DelegatePropertyDescriptor(PropertyDescriptor root) : base(root)
        {
           var pi = root.ComponentType.GetProperty(root.Name);

            var getter = pi.GetGetMethod(true);

            if (getter != null) PropertyGetter = CreateOpenGetter(pi);

            var setter = pi.GetSetMethod(true);

            if (setter != null) PropertySetter = CreateOpenSetter(pi);
        }

        protected Func<object, object> PropertyGetter { get; private set; }

        protected Action<object, object> PropertySetter { get; private set; }

        private static Func<object, object> CreateOpenGetter(PropertyInfo propInfo)
        {
            if (propInfo == null) throw new ArgumentNullException("propInfo");
            if (propInfo.GetGetMethod(true) == null || propInfo.GetGetMethod(true).IsStatic)
            {
                throw new InvalidOperationException("Invalid PropertyInfo.");
            }

            var instanceParameter = Expression.Parameter(typeof(object), "i");
            var convertedInstanceParameter = Expression.ConvertChecked(instanceParameter, propInfo.DeclaringType);

            var propertyExpression1 = Expression.Property(convertedInstanceParameter, propInfo);
            var convertedProperty = Expression.ConvertChecked(propertyExpression1, typeof(object));

            var get = Expression.Lambda<Func<object, object>>(
                convertedProperty,
                instanceParameter
                );

            return get.Compile();
        }

        private static Action<object, object> CreateOpenSetter(PropertyInfo propInfo)
        {
            if (propInfo == null) throw new ArgumentNullException("propInfo");
            if (propInfo.GetGetMethod(true) == null || propInfo.GetGetMethod(true).IsStatic)
            {
                throw new InvalidOperationException("Invalid PropertyInfo.");
            }

            var instanceParameter = Expression.Parameter(typeof(object), "i");
            var convertedInstanceParameter = Expression.ConvertChecked(instanceParameter, propInfo.DeclaringType);

            var valueParameter = Expression.Parameter(typeof(object), "v");
            var convertedValueParameter = Expression.ConvertChecked(valueParameter, propInfo.PropertyType);

            var propertyExpression = Expression.Property(convertedInstanceParameter, propInfo);

            var set = Expression.Lambda<Action<object, object>>(
                Expression.Assign(
                    propertyExpression,
                    convertedValueParameter
                    ),
                instanceParameter,
                valueParameter
                );

            return set.Compile();
        }

        public override object GetValue(object component)
        {
            if (PropertyGetter != null) return PropertyGetter(component);

            throw new InvalidOperationException("No getter defined.");
        }

        public override void SetValue(object component, object value)
        {
            if (PropertySetter != null)
            {
                PropertySetter(component, value);
            }
            else
            {
                throw new InvalidOperationException("No setter defined.");
            }
        }
    }
}